 /*******************************************************************************
  * Copyright (c) 2006, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.menus;

 import java.util.ArrayList ;
 import java.util.HashMap ;
 import java.util.Iterator ;
 import java.util.List ;
 import java.util.Map ;

 import org.eclipse.core.runtime.CoreException;
 import org.eclipse.core.runtime.IConfigurationElement;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.menus.IWidget;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.layout.RowLayout;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.ui.internal.WindowTrimProxy;
 import org.eclipse.ui.internal.WorkbenchWindow;
 import org.eclipse.ui.internal.layout.IWindowTrim;
 import org.eclipse.ui.internal.layout.TrimLayout;
 import org.eclipse.ui.internal.misc.StatusUtil;
 import org.eclipse.ui.menus.AbstractWorkbenchTrimWidget;
 import org.eclipse.ui.menus.IMenuService;
 import org.eclipse.ui.menus.IWorkbenchWidget;
 import org.eclipse.ui.statushandlers.StatusManager;

 /**
  * <p>
  * An implementation that supports 'trim' elements defined in using the
  * <code>org.eclipse.ui.menus</code> extension point.
  * </p>
  * <p>
  * This class is not intended to be used outside of the
  * <code>org.eclipse.ui.workbench</code> plug-in.
  * </p>
  *
  * @since 3.2
  */
 public class TrimBarManager2 {

     /**
      * A List of the URI's representing the trim areas
      */
     private MenuLocationURI[] trimAreaURIs = {
             new MenuLocationURI("toolbar:command1"), //$NON-NLS-1$
 new MenuLocationURI("toolbar:command2"), //$NON-NLS-1$
 new MenuLocationURI("toolbar:vertical1"), //$NON-NLS-1$
 new MenuLocationURI("toolbar:vertical2"), //$NON-NLS-1$
 new MenuLocationURI("toolbar:status"), //$NON-NLS-1$
 };

     /**
      * The SWT 'side' corresponding to a URI
      */
     int[] swtSides = { SWT.TOP, SWT.TOP, SWT.LEFT, SWT.RIGHT, SWT.BOTTOM };
     /**
      * The window on which this menu manager exists; never <code>null</code>.
      */
     private STrimBuilder fTrimBuilder;

     private WorkbenchMenuService fMenuService;

     private boolean fDirty;

     /**
      * Constructs a new instance of <code>TrimBarManager</code>.
      *
      * @param window
      * The window on which this menu manager exists; must not be
      * <code>null</code>.
      */
     public TrimBarManager2(final WorkbenchWindow window) {
         if (window == null) {
             throw new IllegalArgumentException ("The window cannot be null"); //$NON-NLS-1$
 }

         // Remember the parameters
 fMenuService = (WorkbenchMenuService) window.getWorkbench().getService(
                 IMenuService.class);
         fTrimBuilder = new STrimBuilder(window);

         // New layouts are always 'dirty'
 fDirty = true;
     }

     /**
      * Hacked version of the update method that allows the hiding of any trim
      * sited at SWT.TOP. This is because the Intro management wants there to be
      * no trim at the top but can only currently indicate this by using the
      * CoolBar's visibility...
      *
      * @param force
      * @param recursive
      * @param hideTopTrim
      */
     public void update(boolean force, boolean recursive, boolean hideTopTrim) {
         if (force || isDirty()) {
             // Re-render the trim based on the new layout
 fTrimBuilder.build(hideTopTrim);
             setDirty(false);
         }
     }

     /**
      * Copied from the <code>MenuManager</code> method...
      *
      * @param force
      * If true then do the update even if not 'dirty'
      * @param recursive
      * Update recursively
      *
      * @see org.eclipse.jface.action.MenuManager#update(boolean, boolean)
      */
     public void update(boolean force, boolean recursive) {
         update(force, recursive, false);
     }

     /**
      * Set the dirty state of the layout
      *
      * @param isDirty
      */
     private void setDirty(boolean isDirty) {
         fDirty = isDirty;
     }

     /**
      * Returns the 'dirty' state of the layout
      *
      * @return Always returns 'true' for now
      */
     private boolean isDirty() {
         return fDirty;
     }

     /**
      * This is a convenience class that maintains the list of the widgets in the
      * group. This allows any position / orientation changes to the group to be
      * passed on to all the widgets for that group.
      *
      * @since 3.2
      *
      */
     private class TrimWidgetProxy extends WindowTrimProxy {

         private List widgets;
         private TrimAdditionCacheEntry cacheEntry;
         private int originalSide;
         private int curSide;

         private Composite parent;

         /**
          * Constructor that takes in any information necessary to implement an
          * IWindowTrim and also has enough state to manage a group with multiple
          * IWidget contributions.
          *
          */
         public TrimWidgetProxy(List widgets, int side, Composite parent,
                 TrimAdditionCacheEntry entry, boolean resizeable) {
             super(parent, entry.getId(), entry.getId(), SWT.TOP | SWT.BOTTOM | SWT.LEFT
                     | SWT.RIGHT, resizeable);

             // Remember our widget structure
 this.widgets = widgets;
             this.curSide = side;
             this.originalSide = side;
             this.parent = parent;
             this.cacheEntry = entry;
         }

         /*
          * (non-Javadoc)
          *
          * @see org.eclipse.ui.internal.WindowTrimProxy#dock(int)
          */
         public void dock(int newSide) {
             // out with the old...
 for (Iterator iter = widgets.iterator(); iter.hasNext();) {
                 IWidget widget = (IWidget) iter.next();
                 widget.dispose();
                 
                 cacheEntry.removeWidget(widget);
             }

             // ...in with the new
 for (Iterator iter = widgets.iterator(); iter.hasNext();) {
                 IWorkbenchWidget widget = (IWorkbenchWidget) iter.next();
                 if (widget instanceof AbstractWorkbenchTrimWidget)
                     ((AbstractWorkbenchTrimWidget)widget).fill(parent, curSide, newSide);
                 else
                     widget.fill(parent);
             }

             curSide = newSide;
             parent.layout();
         }

         /**
          * Disposes all the widgets contributed into this group and then
          * disposes the group's 'proxy' control
          */
         public void dispose() {
             for (Iterator iter = widgets.iterator(); iter.hasNext();) {
                 IWidget widget = (IWidget) iter.next();
                 widget.dispose();

                 // Remove the IWidget from the entry's cache
 cacheEntry.removeWidget(widget);
             }

             getControl().dispose();
         }

         /**
          * @return The side that the trim was declared to be on
          */
         public int getSide() {
             return originalSide;
         }

         /**
          * @return Whether this addition is at the start or end of the
          * containing trim area
          */
         public boolean isAtStart() {
             //Delegate to the cache entry
 return cacheEntry.isAtStart();
         }
     }

     /**
      * A convenience class that implements the 'rendering' code necessary to
      * turn the contributions to the 'trim' bar into actual SWT controls.
      *
      * @since 3.2
      *
      */
     private class STrimBuilder {
         /**
          * The WorkbenchWindow that this builder is for
          */
         private WorkbenchWindow fWindow;

         /**
          * The list of <code>WindowTrimProxy</code> elements currently
          * rendered in the WorkbenchWindow. Used to support the update mechanism
          * (specifically, it's needed to implement the <code>tearDown</code>
          * method).
          */
         private List curGroups = new ArrayList ();

         /**
          * Map to cache which trim has already been initialized
          */
         private Map initializedTrim = new HashMap ();
         
         /**
          * Construct a trim builder for the given WorkbenchWindow
          *
          * @param window
          * The WorkbenchWindow to render the trim on
          */
         public STrimBuilder(WorkbenchWindow window) {
             fWindow = window;
         }

         /**
          * Remove any rendered trim. This method will always be directly
          * followed by a call to the 'build' method to update the contents.
          */
         public void tearDown() {
             // First, remove all trim
 for (Iterator iter = curGroups.iterator(); iter.hasNext();) {
                 TrimWidgetProxy proxy = (TrimWidgetProxy) iter.next();
                 fWindow.getTrimManager().removeTrim(proxy);

                 try {
                     proxy.dispose();
                 } catch (Throwable e) {
                     IStatus status = null;
                     if (e instanceof CoreException) {
                         status = ((CoreException) e).getStatus();
                     } else {
                         status = StatusUtil
                                 .newStatus(
                                         IStatus.ERROR,
                                         "Internal plug-in widget delegate error on dispose.", e); //$NON-NLS-1$
 }
                     StatusUtil
                             .handleStatus(
                                     status,
                                     "widget delegate failed on dispose: id = " + proxy.getId(), StatusManager.LOG); //$NON-NLS-1$
 }
             }

             // Clear out the old list
 curGroups.clear();
         }

         /**
          * Construct the trim based on the contributions.
          *
          * @param layout
          * The new layout information
          * @param hideTopTrim
          * <code>true</code> iff we don't want to display trim
          * contributed into the SWT.TOP area. This is because the
          * 'Intro' View hides the CBanner (and so, presumably, also
          * wants to not show any other trim at the top.
          *
          * @param window
          * The widnow to 'render' the trim into
          *
          */
         public void build(boolean hideTopTrim) {
             tearDown();
             
             for (int i = 0; i < trimAreaURIs.length; i++) {
                 processAdditions(trimAreaURIs[i], hideTopTrim);
             }
         }

         /**
          * @param menuLocationURI
          * @param hideTopTrim
          */
         private void processAdditions(MenuLocationURI trimURI, boolean hideTopTrim) {
             List additions = fMenuService.getAdditionsForURI(trimURI);
             if (additions.size() == 0)
                 return;
             
             int swtSide = getSide(trimURI);
             
             // Dont show trim on the top if it's 'hidden'
 if (swtSide == SWT.TOP && hideTopTrim)
                 return;

             // Each trim addition represents a 'group' into which one or more
 // widgets can be placed...
 for (Iterator iterator = additions.iterator(); iterator.hasNext();) {
                 TrimAdditionCacheEntry trimEntry = (TrimAdditionCacheEntry) iterator.next();
                 String groupId = trimEntry.getId();
                 
                 // Get the list of IConfgurationElements representing
 // widgets in this group
 List widgets = trimEntry.getWidgets();
                 if (widgets.size() == 0)
                     continue;
                 
                 // Create a 'container' composite for the group
 Composite grpComposite = new Composite(fWindow.getShell(), SWT.NONE);
                 grpComposite.setToolTipText(groupId);
                 
                 // Create the layout for the 'group' container...-no- border margins
 RowLayout rl = new RowLayout();
                 rl.marginBottom = rl.marginHeight = rl.marginLeft = rl.marginRight = rl.marginTop = rl.marginWidth = 0;
                 grpComposite.setLayout(rl);
                 
                 // keep track of whether -any- of the widgets are resizeable
 boolean resizeable = false;

                 for (Iterator widgetIter = widgets.iterator(); widgetIter.hasNext();) {
                     IWorkbenchWidget widget = (IWorkbenchWidget) widgetIter.next();
                     IConfigurationElement widgetElement = trimEntry.getElement(widget);
                     if (widget != null) {
                         resizeable |= trimEntry.fillMajor(widgetElement);
                         renderTrim(grpComposite, widget, swtSide);
                     }
                 }

                 // Create the trim proxy for this group
 TrimWidgetProxy groupTrimProxy = new TrimWidgetProxy(widgets,
                         swtSide, grpComposite, trimEntry, resizeable);
                 curGroups.add(groupTrimProxy);

                 // 'Site' the group in its default location
 placeGroup(groupTrimProxy);
             }
         }

         private void placeGroup(final TrimWidgetProxy proxy) {
             // Get the placement parameters
 final int side = proxy.getSide();
             boolean atStart = proxy.isAtStart();

             // Place the trim before any other trim if it's
 // at the 'start'; otherwise place it at the end
 IWindowTrim beforeMe = null;
             if (atStart) {
                 List trim = fWindow.getTrimManager().getAreaTrim(side);
                 if (trim.size() > 0)
                     beforeMe = (IWindowTrim) trim.get(0);
             }

             // Add the group into trim...safely
 try {
                 proxy.dock(side); // ensure that the widgets are properly oriented
 TrimLayout tl = (TrimLayout) fWindow.getShell().getLayout();
                 tl.addTrim(side, proxy, beforeMe);
             } catch (Throwable e) {
                 IStatus status = null;
                 if (e instanceof CoreException) {
                     status = ((CoreException) e).getStatus();
                 } else {
                     status = StatusUtil
                             .newStatus(
                                     IStatus.ERROR,
                                     "Internal plug-in widget delegate error on dock.", e); //$NON-NLS-1$
 }
                 StatusUtil.handleStatus(status, "widget delegate failed on dock: id = " + proxy.getId(), //$NON-NLS-1$
 StatusManager.LOG);
             }
         }

         /**
          * Render a particular SWidget into a given group
          *
          * @param groupComposite
          * The parent to create the widgets under
          * @param widget
          * The SWidget to render
          * @param side
          */
         private void renderTrim(final Composite groupComposite, IWidget iw,
                 final int side) {
             // OK, fill the widget
 if (iw != null) {
                 // The -first- time trim is displayed we'll initialize it
 if (iw instanceof IWorkbenchWidget && initializedTrim.get(iw) == null) {
                     IWorkbenchWidget iww = (IWorkbenchWidget) iw;
                     iww.init(fWindow);
                     initializedTrim.put(iw, iw);
                 }

                 if (iw instanceof AbstractWorkbenchTrimWidget)
                     ((AbstractWorkbenchTrimWidget)iw).fill(groupComposite, SWT.DEFAULT, side);
                 else
                     iw.fill(groupComposite);
             }
         }

         private int getSide(MenuLocationURI uri) {
             for (int i = 0; i < trimAreaURIs.length; i++) {
                 if (trimAreaURIs[i].getRawString().equals(uri.getRawString()))
                     return swtSides[i];
             }
             return SWT.BOTTOM;
         }


         /**
          * Reposition any contributed trim whose id is -not- a 'knownId'. If the
          * id is known then the trim has already been positioned from the stored
          * workbench state. If it isn't then it's a new contribution whose
          * default position may have been trashed by the WorkbenchWindow's
          * 'restoreState' handling.
          *
          * @param knownIds
          * A List of strings containing the ids of any trim that was
          * explicitly positioned during the restore state.
          */
         public void updateLocations(List knownIds) {
             for (Iterator iter = curGroups.iterator(); iter.hasNext();) {
                 TrimWidgetProxy proxy = (TrimWidgetProxy) iter.next();
                 if (!knownIds.contains(proxy.getId())) {
                     placeGroup(proxy);
                 }
             }
         }
     }

     /**
      * Updates the placement of any contributed trim that is -not- in the
      * 'knownIds' list (which indicates that it has already been placed using
      * cached workspace data.
      *
      * Forward on to the bulder for implementation
      */
     public void updateLocations(List knownIds) {
         fTrimBuilder.updateLocations(knownIds);
     }
     
     /**
      * unhook the menu service.
      */
     public void dispose() {
         fMenuService = null;
         fTrimBuilder = null;
     }
 }


